﻿// Copyright (c) 2012, Alexander Endris
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#region Usings

using System.Drawing;

#endregion

namespace NamelessOne.Imaging
{
  /// <summary>
  ///   A structure that is able to contain color information of 16-bit depth
  /// </summary>
  public struct Color16
  {
    #region Public / Attributes

    /// <summary>
    ///   The alpha value
    /// </summary>
    public int Alpha
    {
      get { return alpha; }
      set { alpha = value; }
    }

    /// <summary>
    ///   The blue value
    /// </summary>
    public int Blue
    {
      get { return blue; }
      set { blue = value; }
    }

    /// <summary>
    ///   This value indicates of what depth the original color data was. (Value is in bytes. So a depth of 2 equals 16-Bit)
    /// </summary>
    public byte Depth
    {
      get { return depth; }
      set { depth = value; }
    }

    /// <summary>
    ///   The green value
    /// </summary>
    public int Green
    {
      get { return green; }
      set { green = value; }
    }

    /// <summary>
    ///   The red value
    /// </summary>
    public int Red
    {
      get { return red; }
      set { red = value; }
    }

    #endregion

    #region Public / Constructors

    /// <summary>
    ///   Creates greyscale with the specified depth
    /// </summary>
    /// <param name = "greyscale">Greyscale value</param>
    /// <param name = "depth">Depth of the greyscale value</param>
    public Color16(int greyscale, byte depth)
      : this(greyscale, greyscale, greyscale, depth)
    {
    }

    /// <summary>
    ///   Creates greyscale with the depth of 1
    ///   This will be strechted to 16-Bit.
    /// </summary>
    /// <param name = "greyscale">Greyscale value</param>
    public Color16(byte greyscale)
      : this(greyscale, greyscale, greyscale)
    {
    }

    /// <summary>
    ///   Creates greyscale with the depth of 2
    /// </summary>
    /// <param name = "greyscale">Greyscale value</param>
    public Color16(short greyscale)
      : this(greyscale, greyscale, greyscale)
    {
    }

    /// <summary>
    ///   Creates greyscale with the depth of 4
    ///   This will be made fit into 16-Bit. Thus accuracy gets lost.
    /// </summary>
    /// <param name = "greyscale">Greyscale value</param>
    public Color16(int greyscale)
      : this(greyscale, greyscale, greyscale)
    {
    }

    /// <summary>
    ///   Creates a color with the depth of 4
    ///   This will be made fit into 16-Bit. Thus accuracy gets lost.
    /// </summary>
    /// <param name = "red">Red component</param>
    /// <param name = "green">Green component</param>
    /// <param name = "blue">Blue component</param>
    public Color16(int red, int green, int blue)
      : this(red, green, blue, (1 << 32) - 1, 4)
    {
    }

    /// <summary>
    ///   Creates a color with the depth of 2
    /// </summary>
    /// <param name = "red">Red component</param>
    /// <param name = "green">Green component</param>
    /// <param name = "blue">Blue component</param>
    public Color16(short red, short green, short blue)
      : this(red, green, blue, (1 << 16) - 1, 2)
    {
    }

    /// <summary>
    ///   Creates a color with the depth of 1
    ///   This will be strechted to 16-Bit.
    /// </summary>
    /// <param name = "red">Red component</param>
    /// <param name = "green">Green component</param>
    /// <param name = "blue">Blue component</param>
    public Color16(byte red, byte green, byte blue)
      : this(red, green, blue, 255, 1)
    {
    }


    /// <summary>
    ///   Creates a color with the specified depth.
    ///   If the depth is greater than 2, the values will be brought down into 16-Bit. Thus accuracy gets lost.
    ///   If the depth is smaller than 2, the values will be stretched into 16-Bit.
    /// </summary>
    /// <param name = "red">Red component</param>
    /// <param name = "green">Green component</param>
    /// <param name = "blue">Blue component</param>
    /// <param name = "depth">Depth value</param>
    public Color16(int red, int green, int blue, byte depth)
      : this(red, green, blue, (256 << (8*(depth - 1))) - 1, depth)
    {
    }

    /// <summary>
    ///   Creates a color with the depth of 1
    ///   This will be strechted to 16-Bit.
    /// </summary>
    /// <param name = "red">Red component</param>
    /// <param name = "green">Green component</param>
    /// <param name = "blue">Blue component</param>
    /// <param name = "alpha">Alpha component</param>
    public Color16(byte red, byte green, byte blue, byte alpha)
      : this(red, green, blue, alpha, 1)
    {
    }

    /// <summary>
    ///   Creates a color with the depth of 2
    /// </summary>
    /// <param name = "red">Red component</param>
    /// <param name = "green">Green component</param>
    /// <param name = "blue">Blue component</param>
    /// <param name = "alpha">Alpha component</param>
    public Color16(short red, short green, short blue, short alpha)
      : this(red, green, blue, alpha, 2)
    {
    }

    /// <summary>
    ///   Creates a color with the depth of 4
    ///   This will be made fit into 16-Bit. Thus accuracy gets lost.
    /// </summary>
    /// <param name = "red">Red component</param>
    /// <param name = "green">Green component</param>
    /// <param name = "blue">Blue component</param>
    /// <param name = "alpha">Alpha component</param>
    public Color16(int red, int green, int blue, int alpha)
      : this(red, green, blue, alpha, 4)
    {
    }

    /// <summary>
    ///   Creates a color with the specified depth
    ///   If the depth is greater than 2, the values will be brought down into 16-Bit. Thus accuracy gets lost.
    ///   If the depth is smaller than 2, the values will be stretched into 16-Bit.
    /// </summary>
    /// <param name = "red">Red component</param>
    /// <param name = "green">Green component</param>
    /// <param name = "blue">Blue component</param>
    /// <param name = "alpha">Alpha component</param>
    /// <param name = "depth">Depth value</param>
    public Color16(int red, int green, int blue, int alpha, byte depth)
    {
      this.red = red;
      this.green = green;
      this.blue = blue;
      this.alpha = alpha;
      this.depth = depth;
      Initialize();
    }

    #endregion

    #region Public / Methods

    /// <summary>
    ///   Converts a Color16 to <see cref = "T:System.Drawing.Color" />
    /// </summary>
    /// <param name = "color">The Color16 to convert</param>
    /// <returns>The <see cref = "T:System.Drawing.Color" /></returns>
    public static implicit operator Color(Color16 color)
    {
      return color.ToColor();
    }

    /// <summary>
    ///   Converts a <see cref = "T:System.Drawing.Color" /> to Color16
    /// </summary>
    /// <param name = "color">The Color to convert</param>
    /// <returns>The Color16</returns>
    public static implicit operator Color16(Color color)
    {
      return new Color16(color.R, color.G, color.B, color.A, 1);
    }

    /// <summary>
    ///   Converts a 16-Bit RGB value (565) into a Color16
    /// </summary>
    /// <param name = "a">First byte</param>
    /// <param name = "b">Second byte</param>
    /// <returns>A Color16</returns>
    public static Color16 FromRGB565(byte a, byte b)
    {
      int combined = a | b << 8;

      int red = combined & 0x1F;
      int green = (combined & 0x7E0) >> 5;
      int blue = (combined & 0xF800) >> 11;

      return new Color16(red, green, blue);
    }

    /// <summary>
    ///   Converts this Color16 instance into a byte array.
    ///   The Byte array does not contain 16-Bit values. The Color16 is first converted into a <see
    ///    cref = "T:System.Drawing.Color" />
    /// </summary>
    /// <returns>Byte array</returns>
    public byte[] GetBytes()
    {
      Color c = this;
      return new[] {c.R, c.G, c.B};
    }

    #endregion

    #region Private / Attributes

    private static readonly float EightToSixteenFactor = 256;

    private static readonly float SixteenToEightFactor = 1/256.0f;
    private static readonly float SixteenToThirtytwoFactor = 1/((float) (1 << 16)/(1 << 32));
    private static readonly float SixteenToTwentyfourFactor = 1/((float) (1 << 16)/(1 << 24));
    private static readonly float ThirtythreeToSixteenFactor = (float) (1 << 16)/(1 << 32);
    private static readonly float TwentyfourToSixteenFactor = (float) (1 << 16)/(1 << 24);

    private int alpha;
    private int blue;
    private byte depth;
    private int green;
    private int red;

    #endregion

    #region Private / Methods

    /// <summary>
    ///   Initializes the Color16 and fits all values into the 16-Bit depth
    /// </summary>
    private void Initialize()
    {
      if (depth == 1)
      {
        red = (int) (red*EightToSixteenFactor);
        green = (int) (green*EightToSixteenFactor);
        blue = (int) (blue*EightToSixteenFactor);
        alpha = (int) (alpha*EightToSixteenFactor);
      }
      else if (depth == 3)
      {
        red = (int) (red*TwentyfourToSixteenFactor);
        green = (int) (green*TwentyfourToSixteenFactor);
        blue = (int) (blue*TwentyfourToSixteenFactor);
        alpha = (int) (alpha*TwentyfourToSixteenFactor);
      }
      else if (depth == 4)
      {
        red = (int) (red*ThirtythreeToSixteenFactor);
        green = (int) (green*ThirtythreeToSixteenFactor);
        blue = (int) (blue*ThirtythreeToSixteenFactor);
        alpha = (int) (alpha*ThirtythreeToSixteenFactor);
      }
    }

    /// <summary>
    ///   Converts this Color16 to <see cref = "T:System.Drawing.Color" />
    /// </summary>
    /// <returns>The <see cref = "T:System.Drawing.Color" /></returns>
    private Color ToColor()
    {
      return Color.FromArgb((int) (alpha*SixteenToEightFactor), (int) (red*SixteenToEightFactor),
                            (int) (green*SixteenToEightFactor),
                            (int) (blue*SixteenToEightFactor));
    }

    #endregion
  }
}